using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using FireStarterModeller.Logic;
using Habanero.Base.Exceptions;
using Microsoft.Practices.Unity;
using NUnit.Framework;
using Rhino.Mocks;

namespace FireStarterModeller.Test
{
    public abstract class AbstractFoo : IFoo
    {
        public string Bar
        {
            get;
            set;
        }

        public string GetBar()
        {
            return "bar";
        }
    }
    public class OtherFoo : AbstractFoo
    {
    }
    public abstract class AbstractFooWithManySubs : AbstractFoo
    {
    }

    public class SubFoo1 : AbstractFooWithManySubs
    {
    }
    public class SubFoo2 : AbstractFooWithManySubs
    {
    }

    public abstract class AbstractFooWithNoSubFoos : AbstractFoo
    {
    }
    [TestFixture]
    public class TestAutoResolvingUnityContainer
    {
        [Test]
        public void Test_ResolveWithNormalContainer_ShouldRaiseError()
        {
            //---------------Set up test pack-------------------
            IUnityContainer container = new UnityContainer();
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            try
            {
                container.Resolve<IFoo>();
                Assert.Fail("Expected to throw an ");
            }
                //---------------Test Result -----------------------
            catch (ResolutionFailedException ex)
            {
                StringAssert.Contains("Resolution of the dependency failed", ex.Message);
            }
        }

        [Test]
        public void Test_Resolve_Interface_ShouldReturnImplementedClass()
        {
            //---------------Set up test pack-------------------
            IUnityContainer container = new AutoResolvingUnityContainer();
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            var resolvedObject = container.Resolve<IFoo>();
            //---------------Test Result -----------------------
            Assert.IsInstanceOf(typeof(IFoo), resolvedObject);

        }
        [Test]
        public void Test_Resolve_ConcreteClass_ShouldReturnResolvedClass()
        {
            //---------------Set up test pack-------------------
            IUnityContainer container = new AutoResolvingUnityContainer();
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            var resolvedObject = container.Resolve<Foo>();
            //---------------Test Result -----------------------
            Assert.IsInstanceOf(typeof (Foo), resolvedObject);

        }

        [Test]
        public void Test_Resolve_OtherConcreteClass_ShouldReturnResolvedClass()
        {
            //---------------Set up test pack-------------------
            IUnityContainer container = new AutoResolvingUnityContainer();
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            var resolvedObject = container.Resolve<OtherFoo>();
            //---------------Test Result -----------------------
            Assert.IsInstanceOf(typeof (OtherFoo), resolvedObject);

        }
        [Test]
        public void Test_Resolve_AbstractClass_ShouldReturnResolvedClass()
        {
            //---------------Set up test pack-------------------
            IUnityContainer container = new AutoResolvingUnityContainer();
            //---------------Assert Precondition----------------
            Assert.IsTrue(typeof(AbstractFoo).IsAbstract);
            //---------------Execute Test ----------------------
            var resolvedObject = container.Resolve<AbstractFoo>();
            //---------------Test Result -----------------------
            Assert.IsInstanceOf(typeof (OtherFoo), resolvedObject);
        }


        [Test]
        public void Test_Resolve_AbstractClassHasMultipleSubClasses_ShouldReturnsFirstOneFound()
        {
            //---------------Set up test pack-------------------
            IUnityContainer container = new AutoResolvingUnityContainer();
            //---------------Assert Precondition----------------
            Assert.IsTrue(typeof(AbstractFooWithManySubs).IsAbstract);
            //---------------Execute Test ----------------------
            var resolvedObject = container.Resolve<AbstractFooWithManySubs>();
            //---------------Test Result -----------------------
            Assert.IsInstanceOf(typeof(AbstractFooWithManySubs), resolvedObject);
        }

        [Test]
        public void Test_Resolve_AbstractClassHasNoSubClasses_ShouldThrowResolutionFailure()
        {
            //---------------Set up test pack-------------------
            IUnityContainer container = new AutoResolvingUnityContainer();
            //---------------Assert Precondition----------------
            Assert.IsTrue(typeof(AbstractFooWithNoSubFoos).IsAbstract);
            //---------------Execute Test ----------------------
            try
            {
                container.Resolve<AbstractFooWithNoSubFoos>();
                Assert.Fail("Expected to throw an ResolutionFailedException ");
            }
            //---------------Test Result -----------------------
            catch (ResolutionFailedException ex)
            {
                StringAssert.Contains("Resolution of the dependency failed", ex.Message);
            }
        }

        [Test]
        public void Test_Resolve_WhenHasWhereClause_WhenItemMatches_ShouldReturnItemMatchingWhereClause()
        {
            //---------------Set up test pack-------------------
            Type boToBeExcluded = typeof(SubFoo1);
            Func<Type, bool> whereClause = type => type.Name != boToBeExcluded.Name;
            var expectedBos = TypesImplementingInterface<AbstractFooWithManySubs>().Where(whereClause);

            IUnityContainer container = new AutoResolvingUnityContainer(whereClause);
            //---------------Assert Precondition----------------
            Assert.IsFalse(expectedBos.Contains(boToBeExcluded), "Should not contain");
            //---------------Execute Test ----------------------
            var resolvedObject = container.Resolve<AbstractFooWithManySubs>();
            //---------------Test Result -----------------------
            Assert.IsInstanceOf(typeof(SubFoo2), resolvedObject);
        }

        private static IEnumerable<Type> TypesImplementingInterface<T>()
        {
            return TypesImplementingInterface(typeof(T));
        }

        private static IEnumerable<Type> TypesImplementingInterface(Type desiredType)
        {
            return AppDomain
                .CurrentDomain
                .GetAssemblies()
                .SelectMany(assembly => assembly.GetTypes())
                .Where(type => desiredType.IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract);
        }
    }
}